import java.util.*;


/* Maven is used to pull in these dependencies. */
import com.google.gson.Gson;

import static spark.Spark.*;

/**
 * This MapServer class is the entry point for running the JavaSpark web server for the BearMaps
 * application project, receiving API calls, handling the API call processing, and generating
 * requested images and routes. You should not need to modify this file unless you're
 * doing the Autocomplete part of the project, though you are welcome to do so.
 * This code is using BearMaps skeleton code version 2.0.
 * @author Alan Yao, Josh Hug
 */
public class MapServer {

    /** HTTP failed response. */
    private static final int HALT_RESPONSE = 403;

    //路网数据
    private static final String Wuhan_PATH = "Wuhan_City_Group.osm";
    private static final String OSM_DB_PATH = Wuhan_PATH;
    /**
     * Each route request to the server will have the following parameters
     * as keys in the params map.<br>
     * start_lat : start point latitude,<br> start_lon : start point longitude,<br>
     * end_lat : end point latitude, <br>end_lon : end point longitude.
     **/
    private static final String[] REQUIRED_ROUTE_REQUEST_PARAMS = {"start_lat", "start_lon",
        "end_lat", "end_lon"};

    private static GraphDB graph;
    private static List<Long> route = new LinkedList<>();

    /**
     * Place any initialization statements that will be run before the server main loop here.
     * Do not place it in the main function. Do not place initialization code anywhere else.
     * This is for testing purposes, and you may fail tests otherwise.
     **/
    public static void initialize() {
        //从xml文件创建一个图对象
        graph = new GraphDB(OSM_DB_PATH);
//        rasterer = new Rasterer();
    }

    public static void main(String[] args) {
        initialize();
        staticFileLocation("/page");
        /* Allow for all origin requests (since this is not an authenticated server, we do not
         * care about CSRF).  */
        before((request, response) -> {
            response.header("Access-Control-Allow-Origin", "*");
            response.header("Access-Control-Request-Method", "*");
            response.header("Access-Control-Allow-Headers", "*");
        });


        /* Define the routing endpoint for HTTP GET requests. */
        /**
         * 在spark中，其请求的处理都是由Route来完成的，一个Route由三部分组成：
         *      一个动词，比如get，post，delete，trace等等
         *      一个路径，比如"/route"
         *      回调函数，比如(req, res)
         */
        get("/route", (req, res) -> {
            HashMap<String, Double> params =
                    getRequestParams(req, REQUIRED_ROUTE_REQUEST_PARAMS);
            System.out.println(params);
            route = Router.shortestPath(graph, params.get("start_lon"), params.get("start_lat"),
                    params.get("end_lon"), params.get("end_lat"));
            String directions = getDirectionsText();
            Map<String, Object> routeParams = new HashMap<>();
            routeParams.put("routing_success", !route.isEmpty());
            routeParams.put("directions_success", directions.length() > 0);
            //返回导航的结果
            routeParams.put("directions", directions);
            ///返回路径上的点的经纬度
            routeParams.put("route", getPositions(route));
            //以json的格式与前端交流
            Gson gson = new Gson();
            return gson.toJson(routeParams);
        });

        /* Define the API endpoint for clearing the current route. */
        get("/clear_route", (req, res) -> {
            clearRoute();
            return true;
        });

        /* Define the API endpoint for search */
        get("/search", (req, res) -> {
            Set<String> reqParams = req.queryParams();
            String term = req.queryParams("term");
            Gson gson = new Gson();
            //搜索某一确定点的位置
            if (reqParams.contains("full")) {
                List<Map<String, Object>> data = getLocations(term);
                return gson.toJson(data);
            } else {
                //返回所有以该字符串为前缀的名字
                List<String> matches = getLocationsByPrefix(term);
                return gson.toJson(matches);
            }
        });

        /* Define map application redirect */
        get("/", (request, response) -> {
            response.redirect("/map.html", 301);
            return true;
        });
    }

    //根据点的id获取经纬度
    private static List<HashMap<String, Double>> getPositions(List<Long> id) {
        List<HashMap<String, Double>> positionList = new LinkedList<>();
        for (Long i : id) {
            HashMap<String, Double> position = new HashMap<>();
            Double lon = graph.nodes.get(i).lon;
            Double lat = graph.nodes.get(i).lat;
            position.put("lat", lat);
            position.put("lon", lon);
            positionList.add(position);
        }
        return positionList;
    }

    /**判断请求参数是否符合要求，如果不符合要求，就是请求失败；如果符合要求，就将请求参数加入Map
     * Validate & return a parameter map of the required request parameters.
     * Requires that all input parameters are doubles.
     * @param req HTTP Request.
     * @param requiredParams TestParams to validate.
     * @return A populated map of input parameter to it's numerical value.
     */
    private static HashMap<String, Double> getRequestParams(
            spark.Request req, String[] requiredParams) {
        //request.queryParams获取请求参数的列表
        Set<String> reqParams = req.queryParams();
        HashMap<String, Double> params = new HashMap<>();
        for (String param : requiredParams) {
            //如果不包含服务器查询所必须的参数，就是请求失败
            if (!reqParams.contains(param)) {
                halt(HALT_RESPONSE, "Request failed - parameters missing.");
            } else {
                try {
                    //request.queryParams(param)获取请求信息中参数param对应的值
                    params.put(param, Double.parseDouble(req.queryParams(param)));
                } catch (NumberFormatException e) {
                    e.printStackTrace();
                    halt(HALT_RESPONSE, "Incorrect parameters - provide numbers.");
                }
            }
        }
        return params;
    }

    /**
     * Clear the current found route, if it exists.
     */
    public static void clearRoute() {
        route = new LinkedList<Long>();
    }

    /**
     * In linear time, collect all the names of OSM locations that prefix-match the query string.
     * @param prefix Prefix string to be searched for. Could be any case, with our without
     *               punctuation.
     * @return A <code>List</code> of the full names of locations whose cleaned name matches the
     * cleaned <code>prefix</code>.
     */
    //该方法负责获取指定前缀的字符串名字，而由对应的字符串名字获取实际的点，由getLocations负责
    public static List<String> getLocationsByPrefix(String prefix) {
        return graph.keysWithPrefixOf(prefix);
    }

    /**
     * Collect all locations that match a cleaned <code>locationName</code>, and return
     * information about each node that matches.
     * @param locationName A full name of a location searched for.
     * @return A list of locations whose cleaned name matches the
     * cleaned <code>locationName</code>, and each location is a map of parameters for the Json
     * response as specified: <br>
     * "lat" : Number, The latitude of the node. <br>
     * "lon" : Number, The longitude of the node. <br>
     * "name" : String, The actual name of the node. <br>
     * "id" : Number, The id of the node. <br>
     */
    public static List<Map<String, Object>> getLocations(String locationName) {
        ArrayList<Long> nodes = graph.getLocations(locationName);
        if (nodes == null) return null;
        //将对象转换成键值对的形式，方便后端和前端的交流
        //同一个名字对应的点不唯一，所以使用List来存储所有与该名字对应的点
        List<Map<String, Object>> result = new LinkedList<>();
        for (Long i : nodes) {
            Map<String, Object> nodeInfo = new HashMap<>();
            point node = graph.locations.get(i);
            nodeInfo.put("lat", node.lat);
            nodeInfo.put("lon", node.lon);
            nodeInfo.put("name", node.name);
            nodeInfo.put("id", node.id);
            result.add(nodeInfo);
        }
        return result;
    }

    /**
     * Takes the route of this MapServer and converts it into an HTML friendly
     * String to be passed to the frontend.
     */
    private static String getDirectionsText() {
        //接收一个装着NavigationDirection对象的List
        List<Router.NavigationDirection> directions = Router.routeDirections(graph, route);
        if (directions == null || directions.isEmpty()) {
          return "";
        }
        StringBuilder sb = new StringBuilder();
        int step = 1;
        //NavigationDirection重载了toString，实际显示的格式是:
        //("%s on %s and continue for %.3f miles.", DIRECTIONS[direction], way, distance)
        for (Router.NavigationDirection d: directions) {
            sb.append(String.format("%d. %s <br>", step, d));
            step += 1;
        }
        return sb.toString();
    }
}
